// ==UserScript== // @name TikTok 测试 // @namespace http://tampermonkey.net/ // @version 5.29 // @description 获取 TikTok 数据! // @author // @match https://www.tiktok.com/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_getResourceText // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect tiktok.com // @icon https://iili.io/dy5xjOg.jpg // @require https://code.jquery.com/jquery-3.6.0.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/toastify-js/1.12.0/toastify.min.js // @resource TOASTIFY_CSS https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css // ==/UserScript== (function() { 'use strict'; // 加载 Toastify.js 的 CSS const toastifyCSS = GM_getResourceText('TOASTIFY_CSS'); GM_addStyle(toastifyCSS); // 注入样式到页面 injectStyles(); let currentUrl = window.location.href; let parsedData = {}; // 获取设置值,默认值为 false let autoShowDataPanel = GM_getValue('autoShowDataPanel', false); // 在脚本菜单中添加选项以设置是否自动弹出数据面板 GM_registerMenuCommand('切换自动弹出数据面板', () => { autoShowDataPanel = !autoShowDataPanel; GM_setValue('autoShowDataPanel', autoShowDataPanel); alert(`自动弹出数据面板已${autoShowDataPanel ? '启用' : '禁用'}`); }); // 注入样式到页面 function injectStyles() { if (document.getElementById('customStyles')) { return; // 已经注入样式 } const styleElement = document.createElement('style'); styleElement.id = 'customStyles'; styleElement.type = 'text/css'; styleElement.textContent = ` .button-85 { padding: 0.6em 0.8em; border: none; outline: none; color: rgb(255, 255, 255); background: #111; cursor: pointer; position: fixed; z-index: 10001; border-radius: 10px; user-select: none; -webkit-user-select: none; touch-action: manipulation; } .button-85:before { content: ""; background: linear-gradient( 45deg, #ff0000, #ff7300, #fffb00, #48ff00, #00ffd5, #002bff, #7a00ff, #ff00c8, #ff0000 ); position: absolute; top: -2px; left: -2px; background-size: 400%; z-index: -1; filter: blur(5px); -webkit-filter: blur(5px); width: calc(100% + 4px); height: calc(100% + 4px); animation: glowing-button-85 20s linear infinite; transition: opacity 0.3s ease-in-out; border-radius: 10px; } @keyframes glowing-button-85 { 0% { background-position: 0 0; } 50% { background-position: 400% 0; } 100% { background-position: 0 0; } } .button-85:after { z-index: -1; content: ""; position: absolute; width: 100%; height: 100%; background: #222; left: 0; top: 0; border-radius: 10px; } #tiktokDataContainer { font-family: Arial, sans-serif; } #tiktokDataContainer input { width: calc(100% - 20px); padding: 0.5em; margin-bottom: 10px; border-radius: 5px; border: 1px solid #ccc; } #tiktokDataContainer #refreshButton { background: none; border: none; cursor: pointer; position: absolute; top: 15px; right: 15px; } #tiktokDataContainer #refreshButton img { width: 20px; height: 20px; } #tiktokDataContainer #loadingIndicator { text-align: center; margin-top: 20px; } .copy-btn { background: none; border: none; cursor: pointer; } `; document.head.appendChild(styleElement); } // 创建用于显示数据面板的按钮 function createButton() { // 创建数据按钮 let dataButton = document.querySelector('#tiktokDataButton'); if (!dataButton) { dataButton = document.createElement('button'); dataButton.id = 'tiktokDataButton'; dataButton.className = 'button-85'; dataButton.innerHTML = '🤓'; dataButton.style.top = '10px'; dataButton.style.right = '50px'; dataButton.addEventListener('click', () => { toggleDataDisplay(); }); document.body.appendChild(dataButton); console.log('数据按钮已创建并添加到页面。'); } } // 切换数据面板的显示和隐藏 function toggleDataDisplay() { console.log('调用了 toggleDataDisplay'); let dataContainer = document.querySelector('#tiktokDataContainer'); if (dataContainer) { // 如果面板已存在,则移除 dataContainer.style.transform = 'translateX(100%)'; dataContainer.style.opacity = '0'; setTimeout(() => { dataContainer.remove(); }, 500); return; } // 创建新的数据面板 showDataPanel(); } // 显示或更新数据面板 function showDataPanel() { let dataContainer = document.querySelector('#tiktokDataContainer'); if (dataContainer) { // 更新数据面板内容而不关闭面板 updateDataContainerContent(dataContainer, parsedData); } else { // 创建新的数据面板 dataContainer = document.createElement('div'); dataContainer.id = 'tiktokDataContainer'; dataContainer.style.transition = 'transform 0.5s ease-in-out, opacity 0.5s ease-in-out'; dataContainer.style.transform = 'translateX(100%)'; dataContainer.style.opacity = '0'; dataContainer.style.position = 'fixed'; dataContainer.style.top = '60px'; dataContainer.style.right = '20px'; dataContainer.style.width = '300px'; dataContainer.style.maxHeight = '400px'; dataContainer.style.overflowY = 'auto'; dataContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'; dataContainer.style.border = '1px solid #ccc'; dataContainer.style.borderRadius = '8px'; dataContainer.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.1)'; dataContainer.style.padding = '15px'; dataContainer.style.zIndex = '10000'; dataContainer.style.backdropFilter = 'blur(10px)'; dataContainer.style.boxSizing = 'border-box'; // 刷新按钮 const refreshButton = document.createElement('button'); refreshButton.id = 'refreshButton'; refreshButton.title = '刷新数据'; refreshButton.innerHTML = '🔄️'; refreshButton.style.fontSize = '20px'; refreshButton.style.top = '17px'; refreshButton.style.right = '30px'; refreshButton.addEventListener('click', () => { console.log('手动刷新按钮被点击。'); currentUrl = window.location.href; // 提取数据并在提取完成后更新显示 extractStats(true); }); dataContainer.appendChild(refreshButton); // 输入框 const urlInput = document.createElement('input'); urlInput.id = 'tiktokUrlInput'; urlInput.placeholder = '输入 TikTok 视频或用户链接'; urlInput.style.width = '100%' dataContainer.appendChild(urlInput); // 添加键盘事件监听器,当按下回车键时触发数据获取 urlInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { const url = urlInput.value.trim(); if (url) { fetchDataFromUrl(url, () => { showDataPanel(); }); } else { showNotification('请输入有效的 TikTok 视频或用户链接'); } } }); // 内容容器 const contentContainer = document.createElement('div'); contentContainer.id = 'contentContainer'; dataContainer.appendChild(contentContainer); document.body.appendChild(dataContainer); setTimeout(() => { dataContainer.style.transform = 'translateX(0)'; dataContainer.style.opacity = '1'; }, 10); } } function updateDataContainerContent(container, data) { const contentContainer = container.querySelector('#contentContainer'); // 清空现有内容 contentContainer.innerHTML = ''; if (data.loading) { // 显示加载指示器 const loadingIndicator = document.createElement('div'); loadingIndicator.id = 'loadingIndicator'; loadingIndicator.innerHTML = '正在获取数据...'; loadingIndicator.style.color = 'black' contentContainer.appendChild(loadingIndicator); return; } if (Object.keys(data).length === 0) { // 没有数据可显示 contentContainer.innerHTML = '暂无数据'; contentContainer.style.color = 'black'; return; } // 使用模板字符串生成 HTML 内容 const dataHtml = generateDataHtml(data); contentContainer.innerHTML = dataHtml; // 为复制按钮添加事件监听器 const copyButtons = contentContainer.querySelectorAll('.copy-btn'); copyButtons.forEach(button => { button.addEventListener('click', (event) => { const copyValue = event.target.getAttribute('data-copy'); navigator.clipboard.writeText(copyValue).then(() => { showNotification('已复制到剪贴板'); }).catch(err => { console.error('复制失败:', err); }); }); }); } function generateDataHtml(data) { const { accountName, followerCount, diggCount, playCount, commentCount, shareCount, collectCount, createTime } = data; const formattedTime = createTime ? new Date(createTime * 1000).toLocaleString() : '未知'; const dataHtml = `

---帖子信息---

发布时间: ${formattedTime}

点赞数: ${diggCount}

播放数: ${playCount}

评论数: ${commentCount}

分享数: ${shareCount}

收藏数: ${collectCount}

---用户信息---

账户名: ${accountName}

粉丝数: ${followerCount}

`; return dataHtml; } // 使用 GM_xmlhttpRequest 请求指定的 TikTok URL function fetchDataFromUrl(url, callback) { // 在数据面板中显示加载指示器 parsedData = { loading: true }; showDataPanel(); const isVideoUrl = /\/video\/\d+/.test(url); if (isVideoUrl) { // 视频链接,先请求视频数据,然后请求用户数据 GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'User-Agent': navigator.userAgent, 'Accept': 'text/html' }, onload: function(response) { if (response.status === 200) { const responseText = response.responseText; const scriptMatch = responseText.match(/